package net.dwade.dubbo.adapter.internal;

import net.dwade.dubbo.adapter.ServiceAdapterProxyFactory;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * jdk实现
 * @author huangxf
 * @date 2017年7月24日
 */
public class EsbServiceAdapterProxyFactory implements ServiceAdapterProxyFactory, BeanFactoryAware {
	
	private static Logger logger = LoggerFactory.getLogger( EsbServiceAdapterProxyFactory.class );
	
	private static final String PREFIX_ESB = "szf0000_";
	
	private static final char SEPARATOR = '_';
	
	/**
	 * consumer的调用provider的地址信息
	 */
	private String address;
	
	/** 创建连接的超时时间 */
	public static final int CONNECTION_TIMEOUT = 3 * 1000;

	/** 从连接池中请求连接的超时时间 */
	public static final int REQUEST_TIMEOUT = 3 * 1000;

	/** 请求服务器响应的超时时间 */
	public static final int SOCKET_TIMEOUT = 3 * 1000;
	
	private BeanFactory beanFactory;
	
	private ServiceRequestResponseProcessor serviceHandler = new EsbParamConvertHandler();
	
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}
	
	public void setAddress(String address) {
		this.address = address;
	}
	
	public void setServiceHandler( ServiceRequestResponseProcessor serviceHandler ) {
		this.serviceHandler = serviceHandler;
	}

	@Override
	public Object createProviderProxy(Object delegate, Class<?> interfaceClass, Class<?> adapterInterfaceClass) {
		
		InvocationHandler handler = new ProviderInvocationHandler( delegate, interfaceClass );
		
		// 动态生成接口的实现类，需要实现InitializingBean接口，便于对ProviderInvocationHandler中处理引用的bean
		Object proxy = Proxy.newProxyInstance( this.getClass().getClassLoader(), 
				new Class<?>[]{ adapterInterfaceClass, InitializingBean.class }, handler );
		
		return proxy;
	}

	@Override
	public Object createConsumerProxy(Object delegate, Class<?> interfaceClass, Class<?> adapterInterfaceClass) {
		
		InvocationHandler handler = new ConsumerInvocationHandler( delegate, adapterInterfaceClass );
		
		// 动态生成接口的实现类
		Object proxy = Proxy.newProxyInstance( this.getClass().getClassLoader(), 
				new Class<?>[]{ interfaceClass }, handler );
		
		return proxy;
		
	}
	
	/**
	 * provider的InocationHandler，用于调用具体的service方法
	 * @author huangxf
	 * @date 2017年7月25日
	 */
	private class ProviderInvocationHandler implements InvocationHandler, InitializingBean {
		
		private Object delegate;
		
		private final Class<?> interfaceClass;
		
		private ConcurrentMap<String, Method> stringNameToMethod = new ConcurrentHashMap<String, Method>();
		
		public ProviderInvocationHandler(Object delegate, Class<?> interfaceClass) {
			this.delegate = delegate;
			this.interfaceClass = interfaceClass;
			Method[] delegateMethods = interfaceClass.getMethods();
			for ( Method method : delegateMethods ) {
				stringNameToMethod.put( method.getName(), method );
			}
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if ( isToStringMethod( method ) ) {
				return this.toString();
			}
			if ( StringUtils.equals( "afterPropertiesSet", method.getName() ) ) {
				this.afterPropertiesSet();
			}
			Method delegateMethod = stringNameToMethod.get( method.getName() );
			logger.info("获取ESB入参数据:{}", args);
			Object[] objArgs = this.resolveArguments( delegateMethod, args );
			Object result = null;
			try {
				result = delegateMethod.invoke( delegate, objArgs );
			} catch (Exception e) {
				//在调用dubbo服务发生异常时将异常对象作为被代理对象的返回值，交由consumer进行处理
				result = e;
			}
			
			String transResult = serviceHandler.handleProviderReturnValue(method, result);
			logger.info("转换为ESB出参结果:{}", transResult);
			return transResult;
		}
		
		protected Object[] resolveArguments( Method method, Object[] stringArgs ) {
			if ( method.getParameterTypes().length == 0 ) {
				return new Object[ 0 ];
			}
			String json = (String) stringArgs[0];
			//String类型参数转换为Object类型参数
			Object[] argument = serviceHandler.resolveProviderArguments(method, json);
			return argument;
		}

		@Override
		public String toString() {
			return interfaceClass.getName() + '@' + this.hashCode();
		}

		@Override
		public void afterPropertiesSet() throws Exception {
			if ( this.delegate instanceof String ) {
				String beanName = (String)delegate; 
				this.delegate = beanFactory.getBean( (String)beanName, interfaceClass );
				logger.info( "Resolve delegate: '{}'-->'{}'", beanName, delegate );
			}
		}
		
	}
	
	/**
	 * 消费者InocationHandler，用于调用具体的dubbo服务
	 * @author huangxf
	 * @date 2017年7月25日
	 */
	private class ConsumerInvocationHandler implements InvocationHandler {
		
		private final Object delegate;
		
		private final Class<?> adapterInterfaceClass;
		
		private ConcurrentMap<String, Method> stringNameToMethod = new ConcurrentHashMap<String, Method>();
		
		public ConsumerInvocationHandler( Object delegate, Class<?> adapterInterfaceClass ) {
			this.delegate = delegate;
			this.adapterInterfaceClass = adapterInterfaceClass;
			Method[] delegateMethods = adapterInterfaceClass.getMethods();
			for ( Method method : delegateMethods ) {
				stringNameToMethod.put( method.getName(), method );
			}
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

			Method delegateMethod = stringNameToMethod.get( method.getName() );
			if ( delegateMethod == null ) {
				if ( isToStringMethod( method ) ) {
					return this.toString();
				}
				return method.invoke( delegate, args );
			}
			
			//"szf0000_" + 接口服务名 + "_" + 方法名   eg:szf0000_PaymentService_payOff
			String serviceURL = this.buildServiceUrl( method );
			
			//入参转换，Object --> String
			Object[] objArgs = this.resolveRequestArguments( delegateMethod, args );
			logger.info( "发起ESB服务URL：{}，请求数据：{}", serviceURL, objArgs[0] );
			
			//这里的data已经是json格式的数据，只需要转换为String
			String jsonReq = String.valueOf(objArgs[0]);
			
			// httpClient调用esb服务
			String result = postWithESB(serviceURL, jsonReq);
			
			//出参转换，String --> Object
			objArgs = this.resolveResponseArguments(method, result);
			return objArgs[0];
		}
		
		protected Object[] resolveRequestArguments( Method method, Object[] objectArgs ) {
			if ( method.getParameterTypes().length == 0 ) {
				return new String[ 0 ];
			}
			String result = serviceHandler.handleConsumerArguments(method, objectArgs);
			return new String[] {result};
		}
		
		protected Object[] resolveResponseArguments( Method method, String result ) {
			if ( method.getParameterTypes().length == 0 ) {
				return new Object[ 0 ];
			}
			//String类型参数转换为Object类型参数
			Object argument = serviceHandler.resolveConsumerReturnValue(method, result);
			return new Object[]{ argument };
		}
		
		/**
		 * 获取调用esb的服务地址
		 * @param method
		 * @return
		 */
		protected String buildServiceUrl( Method method ) {
			StringBuilder url = new StringBuilder( 64 );
			url.append( EsbServiceAdapterProxyFactory.this.address ).append( '/' );
			url.append( PREFIX_ESB ).append( method.getDeclaringClass().getSimpleName() ).append( SEPARATOR );
			url.append( method.getName() );
			return url.toString();
		}
		
		/**
		 * 发起ESB调用
		 * @param serviceURL
		 * @return
		 */
		protected String postWithESB(String serviceURL, String json) {
			CloseableHttpClient httpClient = null;
			CloseableHttpResponse response = null;
			HttpEntity httpEntity = null;
			String result = null;

			// 这里应该使用连接池
			try {
				HttpPost httpPost = new HttpPost( serviceURL );
				httpPost.setConfig( RequestConfig.custom().setConnectTimeout( CONNECTION_TIMEOUT )
						.setConnectionRequestTimeout( REQUEST_TIMEOUT ).setSocketTimeout( SOCKET_TIMEOUT ).build() );
				httpPost.setEntity( new StringEntity( json, ContentType.APPLICATION_JSON ) );
				httpClient = HttpClients.custom().build();
				response = httpClient.execute( httpPost );
				httpEntity = response.getEntity();
				result = EntityUtils.toString( httpEntity, "UTF-8" );
			} catch ( Exception e ) {
				throw new RuntimeException( "调用ESB服务失败，" + e.getMessage(), e );
			} finally {
				IOUtils.closeQuietly( response );
				IOUtils.closeQuietly( httpClient );
			}
			
			StatusLine statusLine = response.getStatusLine();
			int statuCode = statusLine.getStatusCode();
			//针对ESB服务调用返回200 和 500 作为正常返回，其余为不正常返回
			if ( statuCode != 200 && statuCode != 500 ) {
				logger.info("调用ESB服务返回状态码：{},状态行：{}", statuCode, statusLine.toString());
				throw new RuntimeException( "调用ESB务返回状态[" + statuCode + "]");
			}
			
			logger.info("调用ESB服务返回结果：{}", result);
			if ( StringUtils.isBlank(result) ) {
				throw new RuntimeException( "调用ESB服务返回结果为空");
			}
			return result;
		}
		
		@Override
		public String toString() {
			return adapterInterfaceClass.getName() + '@' + this.hashCode();
		}
		
	}
	
	/**
	 * 判断是否是toString方法
	 * @param method
	 * @return
	 */
	private boolean isToStringMethod( Method method ) {
		return StringUtils.equals( "toString", method.getName() ) && (method.getParameterTypes().length == 0);
	}

}
